/*
 * 这个文件的代码是雷霄骅的100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ver2（采用SDL2.0）。
 */

#include "sdlplayvideo.h"

#include <stdio.h>
#ifdef __cplusplus
#define __STDC_CONSTANT_MACROS
#define UINT64_C
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include <libavutil/imgutils.h>
#include "SDL2/SDL.h"
#include "SDL2/SDL_thread.h"
#ifdef __cplusplus
}
#endif

//Refresh Event
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)

int thread_exit = 0;
int thread_pause=0;

int sfp_refresh_thread(void *opaque)
{
    thread_exit=0;
    thread_pause=0;

    //没有按下退出或暂停键，发送更新事件。
    while (!thread_exit) {
        if(!thread_pause){
            SDL_Event event;
            event.type = SFM_REFRESH_EVENT;
            SDL_PushEvent(&event);
        }
        SDL_Delay(40);
    }
    thread_exit=0;
    thread_pause=0;

    //Break
    SDL_Event event;
    event.type = SFM_BREAK_EVENT;
    SDL_PushEvent(&event);

     return 0;
}

int main_SDLPlayVideo(const char * src)
{
    AVFormatContext	*pFormatCtx;
    int				i, videoindex;
    AVCodecContext	*pCodecCtx;
    AVCodec			*pCodec;
    AVFrame	*pFrame,*pFrameYUV;
    unsigned char *out_buffer;
    AVPacket *packet;
    int ret, got_picture;

    int screen_w,screen_h;
    SDL_Window *screen;
    SDL_Renderer* sdlRenderer;
    SDL_Texture* sdlTexture;
    SDL_Rect sdlRect;
    SDL_Thread *video_tid;
    SDL_Event event;

    struct SwsContext *img_convert_ctx;

//    char filepath[]="zhy.flv";

    av_register_all();
    avformat_network_init();
    pFormatCtx = avformat_alloc_context();

    if(avformat_open_input(&pFormatCtx,src,NULL,NULL)!=0)
    {
        printf("Couldn't open input stream.（无法打开输入流）\n");
        return -1;
    }

    if(avformat_find_stream_info(pFormatCtx,NULL)<0)
    {
        printf("Couldn't find stream information.（无法获取流信息）\n");
        return -1;
    }

    videoindex=-1;
    for(i=0; i<pFormatCtx->nb_streams; i++)
        if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
        {
            videoindex=i;
            break;
        }
    if(videoindex==-1)
    {
        printf("Didn't find a video stream.（没有找到视频流）\n");
        return -1;
    }

    pCodecCtx=pFormatCtx->streams[videoindex]->codec;
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec==NULL)
    {
        printf("Codec not found.（没有找到解码器）\n");
        return -1;
    }
    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
    {
        printf("Could not open codec.（无法打开解码器）\n");
        return -1;
    }

    packet=(AVPacket *)av_malloc(sizeof(AVPacket));
    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();

    //设置数据转换参数,用于后面sws_scale函数进行像素转换时指定转换格式参数。
    //pFrame是解码后的帧，像素格式是AVCodecContext 结构体中的 pix_fmt 成员，
    //不一定是YUV420P的格式，这里转成YUV420P格式。
    //pFrame --> pFrameYUV  像素格式转换。
    img_convert_ctx = sws_getContext(
                pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, //源地址长宽以及数据格式
                pCodecCtx->width, pCodecCtx->height, //目的地址长宽以及数据格式
                AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

    //分配空间,一帧图像数据大小
    //解码转换像素格式后的视频帧pFrameYUV需要一个缓存buffer，
    //下一行代码将pFrameYUV和缓存out_buffer关联。
    out_buffer=(unsigned char *)av_malloc(
                av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1));
    // 将pFrameYUV的数据按AV_PIX_FMT_YUV420P格式自动“关联”到buffer，
    // 即pFrameYUV中的数据改变了，out_buffer中的数据也会相应的改变。
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,
                         AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height,1);

    printf("---------------- File Information ---------------\n");
    av_dump_format(pFormatCtx,0,src,0);
    printf("-------------------------------------------------\n");

    //------------SDL----------------
    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
        printf( "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    //SDL 2.0 Support for multiple windows
    screen_w = pCodecCtx->width;
    screen_h = pCodecCtx->height;
    screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        screen_w, screen_h,
        SDL_WINDOW_OPENGL);
    if(!screen) {
        printf("SDL: could not create window - exiting:%s\n",SDL_GetError());
        return -1;
    }

    sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
    //IYUV: Y + U + V  (3 planes)
    //YV12: Y + V + U  (3 planes)
    sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);

    sdlRect.x = 0;
    sdlRect.y = 0;
    sdlRect.w = screen_w;
    sdlRect.h = screen_h;

    video_tid = SDL_CreateThread(sfp_refresh_thread,NULL,NULL);
    //------------SDL End------------

    //Event Loop
    for (;;)
    {
        SDL_WaitEvent(&event);
        if(event.type==SFM_REFRESH_EVENT){
            while(1){
                if(av_read_frame(pFormatCtx, packet)<0)
                    thread_exit=1;

                if(packet->stream_index==videoindex)
                    break;
            }

            //这是ffmpeg2.x旧版本的解码函数。
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
            if(ret < 0){
                printf("Decode Error.\n");
                return -1;
            }

            if(got_picture){
                //像素格式转换 pFrame --> pFrameYUV
                sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data,
                          pFrame->linesize, 0,
                          pCodecCtx->height,
                          pFrameYUV->data,
                          pFrameYUV->linesize);

                SDL_UpdateTexture( sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0] );
                SDL_RenderClear( sdlRenderer );
                SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, NULL);
                SDL_RenderPresent( sdlRenderer );
            }
            av_free_packet(packet);
        }else if(event.type==SDL_KEYDOWN){
            if(event.key.keysym.sym==SDLK_SPACE)  //Pause
                thread_pause=!thread_pause;
        }else if(event.type==SDL_QUIT){
            thread_exit=1;
        }else if(event.type==SFM_BREAK_EVENT){
            break;
        }
    }

    sws_freeContext(img_convert_ctx);

    SDL_Quit();

    av_free(out_buffer);
    av_free(pFrameYUV);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    return 0;
}
